package kotan;

import java.awt.Desktop;
import java.awt.EventQueue;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingWorker;

import kotan.datastore.api.Key;
import kotan.model.DatastoreModel;
import kotan.model.EntityModel;
import kotan.model.KindModel;
import kotan.server.embedded.DevAppServer;
import kotan.server.embedded.ServerStartupException;
import kotan.service.AuthoricationRequiredException;
import kotan.service.DevEntityPersistentManager;
import kotan.service.EntityPersistentManager;
import kotan.service.HttpClientManager;
import kotan.service.ProdEntityPersistentManager;
import kotan.view.AppFrame;
import kotan.view.StartupDialog;
import kotan.view.auth.AuthDialog;

import org.apache.http.client.methods.HttpGet;

/**
 * Kotan, a datastore editor for AppEngine.
 * 
 * @author shuji.w6e
 * @since 1.0
 */
public class Kotan implements KotanService {
    
    /** logger */
    private static Logger logger = Logger.getLogger(Kotan.class.getName());

    /** a singleton instance of the application */
    private static final AtomicReference<Kotan> INSTANCE = new AtomicReference<Kotan>();

    /**
     * Entry point of Kotan.
     * @param args
     */
    public static void main(String[] args) {
        logger.info("Kotan start.");
        try {
            Kotan main = new Kotan();
            INSTANCE.set(main);
            main.initalize(args);
            main.startup();
        } catch (InitalizeException e) {
            System.err.println(e.getMessage());
            System.err.println("[Usage]");
            System.err
                .println("java kotan.Kotan -production=<TrueOrFalse> -host=<HostName> -port=<PortNo> -nameSpace=<NameSpaceName> -kinds=<Kind1,Kind2>");
        }
    }

    /**
     * Get an instance of the application.
     * @return singleton instance.
     */
    public static Kotan get() {
        return INSTANCE.get();
    }

    private HttpClientManager httpClientManager;
    private EntityPersistentManager entityPersistentManager;
    private volatile AppFrame frame;
    private final DatastoreModel datastore = new DatastoreModel();
    private volatile AppEngineEnv env;
    private volatile Options options;
    private volatile DevAppServer devAppServer;

    public void initalize(String[] args) throws InitalizeException {
        if (logger.isLoggable(Level.FINE)) logger.fine("initalize");
        try {
            options = new Options(args);
            env = options.env;
            httpClientManager = new HttpClientManager();
            if (options.production) return;
            EventQueue.invokeAndWait(new Runnable() {

                @Override
                public void run() {
                    StartupDialog.showDialog(env);
                }
            });
            devAppServer = new DevAppServer(env);
            devAppServer.start();
        } catch (ServerStartupException e) {
            throw new InitalizeException(e);
        } catch (InterruptedException e) {
            throw new InitalizeException(e);
        } catch (InvocationTargetException e) {
            throw new InitalizeException(e);
        }
    }

    // TODO needs kicker
    public void showAdminOnBrowser() {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                try {
                    // TODO hostname
                    String uri = "http://localhost:" + env.port + "/_ah/admin/datastore";
                    Desktop.getDesktop().browse(new URI(uri));
                } catch (Throwable e) {
                    // do nothing
                    e.printStackTrace();
                }
            }
        });
    }

    private void watitForStartup() {
        for (int retry = 0; retry < 10; retry++) {
            try {
                httpClientManager.getHttpClient().execute(httpClientManager.getHttpHost(), new HttpGet("/"));
                break;
            } catch (IOException e) {
                // not running / try retry.
            }
            logger.info("server is not start.");
            try {
                Thread.sleep(1000L); // 1s
            } catch (InterruptedException e) {
            }
            logger.info("retry.");
        }
    }

    public void startup() {
        entityPersistentManager =
            options.production ? new ProdEntityPersistentManager(httpClientManager) : new DevEntityPersistentManager(
                httpClientManager);
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                setupGui();
                watitForStartup();
                for (String kind : options.kinds) {
                    load(kind);
                }
                
            }
        });
    }

    private void setupGui() {
        frame = new AppFrame();
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosed(WindowEvent e) {
                shutdown();
            }
        });
        frame.setTitle("Kotan - Datastore Viewer for Google App Engine.");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setSize(800, 600);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        frame.toFront();
    }

    public void shutdown() {
        logger.info("shutdown start.");
        if (httpClientManager != null) httpClientManager.shutdown();
        if (devAppServer != null) devAppServer.shutdown();
        logger.info("shutdown end.");
    }

    public DatastoreModel getDatastore() {
        return datastore;
    }

    public AppEngineEnv getEnv() {
        return env;
    }

    public AppFrame getMainFrame() {
        return frame;
    }

    public KotanService getService() {
        return this;
    }

    @Override
    public void load(final String kind) {
        frame.activateProgressBar();
        frame.addEntityViewer(kind);
        new SwingWorker<KindModel, Void>() {

            @Override
            protected KindModel doInBackground() throws Exception {
                try {
                    KindModel model = entityPersistentManager.load(kind);
                    datastore.add(model);
                    return model;
                } catch (AuthoricationRequiredException e) {
                    e.printStackTrace();
                    // auth dialog
                    class AuthInfoHolder {
                        volatile AuthInfo authInfo = AuthInfo.NO_AUTH;
                    }
                    final AuthInfoHolder holder = new AuthInfoHolder();
                    String email = options.email;
                    for (int retry = 0; retry < 3; retry++) {
                        final String defaultEmail = email;
                        EventQueue.invokeAndWait(new Runnable() {
                            @Override
                            public void run() {
                                holder.authInfo = AuthDialog.showDialog(defaultEmail);
                            }
                        });
                        if (holder.authInfo == AuthInfo.NO_AUTH) {
                            return null;
                        }
                        try {
                            entityPersistentManager.authorize(holder.authInfo);
                            KindModel model = entityPersistentManager.load(kind);
                            datastore.add(model);
                            return model;
                        } catch (AuthoricationRequiredException e2) {
                            e2.printStackTrace();
                            email = holder.authInfo.getEmail();
                            JOptionPane.showMessageDialog(
                                frame,
                                "Wrong email or password.",
                                "Authorication failed.",
                                JOptionPane.ERROR_MESSAGE);
                        }
                    }
                    return null;
                }
            }

            @Override
            protected void done() {
                try {
                    KindModel model = get();
                    if (model == null) { // Login failed
                        frame.dispose();
                        return;
                    }
                    frame.setKindModel(model);
                    frame.disactivateProgressBar();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }

        }.execute();
    }

    @Override
    public void uploadAndDelete(final List<EntityModel> updateList, final List<Key> deleteList) {
        frame.activateProgressBar();
        frame.resetProgress(updateList.size() + deleteList.size());
        new SwingWorker<Void, Integer>() {

            @Override
            protected Void doInBackground() throws Exception {
                int count = 0;
                for (EntityModel model : updateList) {
                    entityPersistentManager.upload(model);
                    count++;
                    publish(count);
                }
                for (Key key : deleteList) {
                    entityPersistentManager.delete(key);
                    count++;
                    publish(count);
                }
                return null;
            }

            @Override
            protected void done() {
                frame.disactivateProgressBar();
            }

            @Override
            protected void process(List<Integer> chunks) {
                if (chunks.isEmpty()) return;
                frame.setProgress(chunks.get(chunks.size() - 1));
            }

        }.execute();
    }
}
